package cn.dlc.com.account.service.impl;

import cn.dlc.com.account.dao.AccountDao;
import cn.dlc.com.account.entity.Account;
import cn.dlc.com.account.service.AccountService;
import cn.dlc.com.feign.Tcc2BankAccountService;
import cn.dlc.com.log.dao.LocalCancelLogDao;
import cn.dlc.com.log.dao.LocalConfirmLogDao;
import cn.dlc.com.log.dao.LocalTryLogDao;
import cn.dlc.com.log.entity.LocalCancelLog;
import cn.dlc.com.log.entity.LocalTryLog;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hmily.annotation.Hmily;
import org.dromara.hmily.common.bean.context.HmilyTransactionContext;
import org.dromara.hmily.core.concurrent.threadlocal.HmilyTransactionContextLocal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;


/**
 * @author 邓立川
 * @date 2021/1/19 9:49
 * @desc 添加当前类的描述信息
 */
@Service
@Transactional
@Slf4j
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private LocalTryLogDao tryLogDao;

    @Autowired
    private LocalCancelLogDao cancelLogDao;

    @Autowired
    private LocalConfirmLogDao confirmLogDao;

    @Autowired
    private Tcc2BankAccountService tcc2BankAccountService;

    /**
     *  这个就是try方法，
     * @param accountNo 账号
     * @param amount 金额
     */
    @Override
    @Hmily(confirmMethod = "confirm",cancelMethod = "cancel")
    public void updateAccount(String accountNo, Double amount) {

        // 获取全局事务id
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();

        // 【try的幂等判断】 判断有没有执行过try
        if(tryLogDao.existsById(transId)) {
            log.info("bank1 的try已经执行过了，不需要执行了: {}",transId);
            return ;
        }

        // 【try的悬挂处理】 判断有没有执行过cancel或者confirm
        if(cancelLogDao.existsById(transId) || confirmLogDao.existsById(transId)){
            log.info("bank1的cancel或者confirm已经执行了，不需要执行try：{}",transId);
            return ;
        }

        // 1.先判断有没有这么多钱
        synchronized (this){
            Account account = accountDao.findByAccountNoAndAccountBalanceGreaterThanEqual(accountNo,new BigDecimal(amount));
            if(account != null) {
                // 能够处理(扣钱)
                BigDecimal result = account.getAccountBalance().subtract(new BigDecimal(amount));
                account.setAccountBalance(result);
                accountDao.save(account);

            } else {
                throw new RuntimeException("没钱了");
            }
        }

        // 增加一条try的日志记录
        tryLogDao.save(new LocalTryLog(transId,new Date()));

        // 远程调用服务
        Boolean transfer = tcc2BankAccountService.transfer(accountNo, amount);
        if(!transfer) {
            throw new RuntimeException("远程调用失败了");
        }

        if(amount == 20) {
            throw new RuntimeException("本地异常了！");
        }
    }

    public void confirm(String accountNo, Double amount){
        // 按照思路，这里是空的，因为在try里面提交了，
        log.info("这里是confirm方法！");
    }

    public void cancel(String accountNo, Double amount){
        // 获取全局事务id
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();

        // 【cancel幂等校验】 判断有没有执行过cancel
        if(cancelLogDao.existsById(transId)) {
            // 执行过了，不再执行
            log.info("cancel执行过了：{}",transId);
            return;
        }

        // 【cancel空回滚处理】 如果try没有执行，cancel不用回滚
        if(!tryLogDao.existsById(transId)) {
            log.info("try没有执行过：{}" ,transId);
            return ;
        }

        // 回滚余额
        synchronized (this){
            Account account = accountDao.findByAccountNo(accountNo);
            if(account != null) {
                // 能够处理
                BigDecimal result = account.getAccountBalance().add(new BigDecimal(amount));
                account.setAccountBalance(result);
                accountDao.save(account);

            } else {
                throw new RuntimeException("没钱了");
            }
        }

        // 保存一条cancel的日志
        cancelLogDao.save(new LocalCancelLog(transId, new Date()));

    }
}
